package org.hilo.boot.app.web;

import java.util.HashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.jretty.apibase.Result;
import org.hilo.boot.core.RefMsg;
import org.hilo.boot.core.UT;
import org.hilo.boot.core.exception.AppException;
import org.hilo.boot.core.util.HiloStackTraceFilter;

import org.jretty.util.JrettyCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

/**
 * 异常集中处理器
 * 
 * @author zollty
 */
abstract public class GlobalExceptionResolver implements HandlerExceptionResolver, Ordered {
    private Logger logger = LoggerFactory.getLogger(getClass());
    // private static final StackTraceElement[] EMPTY_STACK = new StackTraceElement[0];
    private JrettyCache cache = new JrettyCache(128, 1000);
    /**
     * Standard Spring exceptions
     */
    private static Set<Class<?>> springExceptions = new HashSet<>();
    static {
        springExceptions.add(org.springframework.beans.ConversionNotSupportedException.class);
        springExceptions.add(org.springframework.beans.TypeMismatchException.class);
        springExceptions.add(org.springframework.http.converter.HttpMessageNotReadableException.class);
        springExceptions.add(org.springframework.http.converter.HttpMessageNotWritableException.class);
        springExceptions.add(org.springframework.validation.BindException.class);
        springExceptions.add(org.springframework.web.HttpMediaTypeNotAcceptableException.class);
        springExceptions.add(org.springframework.web.HttpMediaTypeNotSupportedException.class);
        springExceptions.add(org.springframework.web.HttpRequestMethodNotSupportedException.class);
        springExceptions.add(org.springframework.web.bind.MethodArgumentNotValidException.class);
        springExceptions.add(org.springframework.web.bind.MissingPathVariableException.class);
        springExceptions.add(org.springframework.web.bind.MissingServletRequestParameterException.class);
        springExceptions.add(org.springframework.web.bind.ServletRequestBindingException.class);
        springExceptions.add(org.springframework.web.context.request.async.AsyncRequestTimeoutException.class);
        springExceptions.add(org.springframework.web.multipart.support.MissingServletRequestPartException.class);
        springExceptions.add(org.springframework.web.servlet.NoHandlerFoundException.class);
    }
    

    @Override
    public int getOrder() {
        return 0;
    }
    
    @Override
    public ModelAndView resolveException(HttpServletRequest request, 
            HttpServletResponse response, Object handler, Exception ex) {
        Result<?> r = null;
        if (ex instanceof AppException) {
            // I18nException，不必记录日志
            r = Result.fail(((AppException) ex).getErrorCode(), 
                    ((AppException) ex).getMessage(request.getLocale()));
            // ex.setStackTrace(EMPTY_STACK); // 清空堆栈信息
        } else {
            String key = UT.Excp.getExceptionSign(ex);
            r = cache.getData(key);
            if (r != null) {
                r.setTimestamp(System.currentTimeMillis());
            } else {
                for (Class<?> cls : springExceptions) {
                    if (cls.isInstance(ex)) {
                        r = Result.fail(new RefMsg().getCode(), 
                                UT.Excp.getExceptionProfile(ex, null));
                        autoLogError(request, r, ex, key);
                        break;
                    }
                }
                
                r = r == null ? handleMoreException(request, ex) : r;
            }
            
            if (r == null) {
                r = Result.fail(new RefMsg().getCode(), 
                        UT.Excp.getExceptionProfile(ex, null));
                autoLogError(request, r, ex, key);
            }
            
        }

        return new ModelAndView(new SimpleJsonView(UT.Json.toJSONString(r)));
    }
    
    /**
     * 记录异常日志 如果最近5秒内有这个相同的异常，则不再记录。5秒钟后，重新计时。
     */
    protected void autoLogError(HttpServletRequest request, Result<?> r, 
            Throwable ex, String key) {
        if (cache.putIfAbsent(key, r)) {
            String uri = request.getRequestURI();
            logger.error(r.getCode() + ": execution at: " + uri + "\n"
                    + UT.Excp.getStackTraceStr(HiloStackTraceFilter.me(), ex));
        }
    }

//    /**
//     * 记录异常日志 如果最近5秒内有这个相同的异常，则不再记录。5秒钟后，重新计时。
//     */
//    protected void autoLogError(HttpServletRequest request, Result<?> r, 
//            Throwable ex, String key, String extraLogMsg) {
//        if (cache.putIfAbsent(key, r)) {
//            String uri = request.getRequestURI();
//            logger.error(r.getCode() + ": execution at: " + uri + ", " + extraLogMsg + "\n"
//                    + ExcpUtils.getStackTraceStr(HiloStackTraceFilter.me(), ex));
//        }
//    }

    
    abstract public Result<?> handleMoreException(HttpServletRequest request, Exception ex);

}
